package com.fundboss.display;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Paint;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;

import model.market.DayMarketInfo;
import model.market.MarketInformation;
import model.market.MarketListener;
import model.market.ModelResults;
import model.market.Position;
import model.util.stats.RegressionCalc.RegressionResult;

import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.LogarithmicAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.IntervalMarker;
import org.jfree.chart.plot.Marker;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.ValueMarker;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYBarRenderer;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.RectangleAnchor;
import org.jfree.ui.TextAnchor;

import com.fundboss.display.GraphDisplayConfig.AssetDisplayConfig;
import com.fundboss.display.GraphDisplayConfig.TraderDisplayConfig;

/**
 * Displays the results of the market run.
 * @author andy
 *
 */
//AMLOW: allow overlay of previous run.
//AMLOW: get a scattergraph of returns versus variance like we use to construct portfolio.
public class GraphResults extends JPanel implements MarketListener{

	private static final int CAPM_FIRST_READ = 10;
	ChartPanel chartPanel;
	ModelResults results;
	GraphDisplayConfig config;
	JFrame display;
	JLabel statusLabel = new JLabel("");
	JPanel leftSide;
	JSplitPane wrapper;

	@Override
	public void newIteration(int period) {
		statusLabel.setText("Processing iteration: "+period);
	}

	@Override
	public void complete(ModelResults execute) {


		//button.setEnabled(true);
		statusLabel.setText("Done!");

		results=execute;
		config = new GraphDisplayConfig(
				results.getResults()[0].getClassPositions().keySet(),
				results.getTraderInformation().getNumberOfAssets());
		config.getIndexPrice().setShow(true);
		JFreeChart lineChart = buildChart();
		displayLineChart(lineChart);
		CheckListPanel checkListPanel = new CheckListPanel(config,this);

		JScrollPane pane = new JScrollPane(checkListPanel);
		pane.setPreferredSize(new Dimension(200,700));
		wrapper.remove(leftSide);
		wrapper.add(pane);
		//leftSide.doLayout();
		//leftSide.repaint();

		wrapper.doLayout();
		wrapper.repaint();

	}

	@Override
	public void error(Exception e) {
		//button.setEnabled(true);
		statusLabel.setText("Sorry: An error occurred");
		System.out.println(e);
		e.printStackTrace();

	}


	public GraphResults(){//ModelResults results){
		statusLabel.setText("Preparing market model");


		chartPanel = new ChartPanel(null, false);

		chartPanel.setPreferredSize(new Dimension(600, 400));
		//display = new JFrame("Results");
		setLayout(new BorderLayout(10,10));
		//display.setSize(1200,800);

		leftSide = new JPanel();
		leftSide.add(statusLabel);


		wrapper = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,leftSide,chartPanel);

		//display.add(pane,BorderLayout.WEST);
		add(wrapper,BorderLayout.CENTER);
		setVisible(true);

		//        display.addWindowListener(new java.awt.event.WindowAdapter() {
		//            public void windowClosing(WindowEvent winEvt) {
		//                // Perhaps ask user if they want to save any unsaved files first.
		//                display.setVisible(false);
		//                display=null;
		//                //AMLOW: how do we get the graphresults out of memory now?
		//            }
		//        });

	}

	public JFreeChart buildChart() {
		MarketInformation traderInformation = results.getTraderInformation();

		NumberAxis xAxis = new NumberAxis("Period");
		xAxis.setAutoRangeIncludesZero(false);

		NumberAxis indexPriceAxis = new NumberAxis("Price");
		XYItemRenderer renderer = new XYLineAndShapeRenderer(true,false);
		XYDataset data = null;
		if(config.getIndexPrice().getShow()){
			data=createXYDataset("Market Index", traderInformation.getIndexPrices(),true);
		}

		int yaxiscount=0;
		if(config.getIndexPrice().getShow()){
			yaxiscount++;
		}


		XYPlot plot = new XYPlot(data, xAxis, indexPriceAxis, renderer);
		plot.setOrientation(PlotOrientation.VERTICAL);

		Color paint = new Color(60,60,110,60);
		Marker firstTradePeriod = new IntervalMarker(0,results.getTraderInformation().getFirstTradePeriod()-1,paint);

		firstTradePeriod.setLabel("Environment Preparation");
		firstTradePeriod.setLabelAnchor(RectangleAnchor.CENTER);
		firstTradePeriod.setLabelTextAnchor(TextAnchor.TOP_LEFT);

		plot.addDomainMarker(firstTradePeriod);

		if(results.getTraderInformation().valueShock()){
			Marker valueShock = new ValueMarker(results.getTraderInformation().getFirstTradePeriod()+20);
			valueShock.setLabel("Asset 0 Value Shock");
			valueShock.setLabelAnchor(RectangleAnchor.TOP_RIGHT);
			valueShock.setLabelTextAnchor(TextAnchor.TOP_LEFT);

			plot.addDomainMarker(valueShock);
		}

		if(results.getTraderInformation().getConfig().getSubtractFractionShock()!=0){
			Marker worthShock = new ValueMarker(results.getTraderInformation().getFirstTradePeriod()+30);
			worthShock.setLabel("Investor Worth shock");
			worthShock.setLabelAnchor(RectangleAnchor.TOP_RIGHT);
			worthShock.setLabelTextAnchor(TextAnchor.TOP_LEFT);

			plot.addDomainMarker(worthShock);
		}

		JFreeChart lineChart = new JFreeChart(null, JFreeChart.DEFAULT_TITLE_FONT,
				plot, true);


		if(config.getIndexVolume().getShow()){
			drawPlot(yaxiscount++, plot, traderInformation.getIndexVolume(), "Market Volume", "Volume",null,new XYBarRenderer());
		}
		//AMLOW: reuse all y axes where appropriate.

		ValueAxis rsqAxis = new NumberAxis("R^2");
		if(config.getAverageRsq().getShow()){
			drawPlot(yaxiscount++, plot, traderInformation.getAverageRsq(), "Average Rsq", "R squared",rsqAxis,null);
		}
		NumberAxis divYieldAxis =  new NumberAxis("Dividend Yield (Annualised)");
		if(config.getIndexDivYield().getShow()){
			List<Double> temp = annualise(traderInformation, traderInformation.getIndexDivYields());
			drawPlot(yaxiscount++, plot, temp, "Index Div Yield", "Dividend Yield (Annualised)",divYieldAxis,null);
		}
		if(config.getIndexVolatility().getShow()){
			List<Double> vol = new ArrayList<Double>();
			for(Double variance:traderInformation.getIndexVariance()){
				vol.add(Math.sqrt(variance));
			}

			drawPlot(yaxiscount++, plot, vol, "Index Vol", "Volatility");
		}
		if(config.getIndexExcessReturns().getShow()){
			drawPlot(yaxiscount++, plot, traderInformation.getIndexReturnsInclCashAndCapitalLessRiskFree(), "Index Excess Return", "Return");
		}

		if(config.getIndexDividend().getShow()){
			List annualise = results.getTraderInformation().getIndexCashflows();
			List<Double> temp = annualise(traderInformation, annualise);
			drawPlot(yaxiscount++, plot, temp , "Index Div.", "Dividend (Annualised)");
		}
		if(config.getRiskFree().getShow()){
			drawPlot(yaxiscount++, plot, traderInformation.getRiskFreeReturns(), "Interest Rate", "Interest Rate");
		}
		if(config.getEquityRiskPremium().getShow()){
			drawPlot(yaxiscount++, plot, traderInformation.getEquityRiskPremiums(), "Equity Risk Premium", "Equity Risk Premium");
		}

		boolean showPrice = false;
		boolean showVolume = false;
		boolean showLiquidity = false;
		boolean showBeta = false;
		boolean showRsq = false;
		boolean showVolatility = false;
		boolean showExcessReturns = false;
		boolean showCashflow = false;
		boolean showDivYield = false;
		boolean showStandardError = false;

		for(Integer assetCount:config.getAssetsConfig().keySet()){
			AssetDisplayConfig assetConfig = config.getAssetsConfig().get(assetCount);
			showPrice = showPrice||assetConfig.getPrice().getShow();
			showVolume = showVolume||assetConfig.getVolume().getShow();
			showLiquidity = showLiquidity||assetConfig.getLiquidity().getShow();
			showBeta = showBeta||assetConfig.getBeta().getShow();
			showRsq = showRsq||assetConfig.getRsq().getShow();
			showVolatility=showVolatility||assetConfig.getVolatility().getShow();
			showExcessReturns=showExcessReturns||assetConfig.getExcessReturns().getShow();
			showCashflow=showCashflow||assetConfig.getCashflow().getShow();
			showStandardError = showStandardError||assetConfig.getStandardError().getShow();
			showDivYield=showDivYield||assetConfig.getDivYield().getShow();

		}

		XYSeriesCollection assetPrices = new XYSeriesCollection();
		if(showPrice){
			//AMLOW: maybe switch back to logarithmic
			constructAssetResultAxis(yaxiscount++, plot, "Asset Price", assetPrices,false,null);
		}
		XYSeriesCollection assetVolumes = new XYSeriesCollection();
		if(showVolume){
			constructAssetResultAxis(yaxiscount++, plot, "Asset Volume", assetVolumes,false,null,true);
		}
		XYSeriesCollection assetLiquidity = new XYSeriesCollection();
		if(showLiquidity){
			constructAssetResultAxis(yaxiscount++, plot, "Asset Liquidity", assetLiquidity,false,null,true);
		}
		XYSeriesCollection assetBeta = new XYSeriesCollection();
		if(showBeta){
			constructAssetResultAxis(yaxiscount++, plot, "Beta", assetBeta,false,null);
		}
		XYSeriesCollection assetRsq = new XYSeriesCollection();
		if(showRsq){
			constructAssetResultAxis(yaxiscount++, plot, "R^2", assetRsq,false,rsqAxis);
		}
		XYSeriesCollection assetVolatility = new XYSeriesCollection();
		if(showVolatility){
			constructAssetResultAxis(yaxiscount++, plot, "Asset Volatility", assetVolatility,false,null);
		}
		XYSeriesCollection assetExcessReturn = new XYSeriesCollection();
		if(showExcessReturns){
			constructAssetResultAxis(yaxiscount++, plot, "Asset Excess Return", assetExcessReturn,false,null);
		}
		XYSeriesCollection assetCashflow = new XYSeriesCollection();
		if(showCashflow){
			constructAssetResultAxis(yaxiscount++, plot, "Asset Dividend (Annualised)", assetCashflow,false,null);
		}
		XYSeriesCollection assetDivYield = new XYSeriesCollection();

		if(showDivYield){
			constructAssetResultAxis(yaxiscount++, plot, "Asset Div Yield (Annualised)", assetDivYield,false,divYieldAxis);
		}
		XYSeriesCollection assetStandardErr = new XYSeriesCollection();
		if(showStandardError){
			constructAssetResultAxis(yaxiscount++, plot, "Asset Std. Err.", assetStandardErr,false,null);
		}

		for(int i=0;i<traderInformation.getAssetPrices().length;i++){
			AssetDisplayConfig assetConfig = config.getAssetsConfig().get(i);
			List<RegressionResult> assetBetas =traderInformation.getAssetCapmResults()[i];

			if(assetConfig.getPrice().getShow()){
				populateDataset("Asset Price "+i, traderInformation.getAssetPrices()[i] ,assetPrices,true);
			}
			if(assetConfig.getVolume().getShow()){
				populateDataset("Asset Volume "+i, traderInformation.getAssetVolume()[i] ,assetVolumes,false);
			}
			if(assetConfig.getLiquidity().getShow()){
				List<Double> list = traderInformation.getAssetLiquidity()[i];
				for(int listCount=0;listCount<list.size();listCount++){
					double liquid=list.get(listCount);
					if(liquid==-1){
						list.set(listCount, (double)0);
					}
				}
				populateDataset("Asset Liquidity "+i, traderInformation.getAssetLiquidity()[i] ,assetLiquidity,false);
			}

			if(assetConfig.getBeta().getShow()){

				XYSeries series = new XYSeries("Asset Beta "+i);
				for(Integer betaCount=CAPM_FIRST_READ;betaCount<assetBetas.size();betaCount++){
					series.add( (double)betaCount,assetBetas.get(betaCount).getBeta());
				}
				assetBeta.addSeries(series);

			}
			if(assetConfig.getRsq().getShow()){

				XYSeries series = new XYSeries("Asset R^2 "+i);
				for(Integer betaCount=CAPM_FIRST_READ;betaCount<assetBetas.size();betaCount++){
					series.add( (double)betaCount,assetBetas.get(betaCount).getRsquared());
				}
				assetRsq.addSeries(series);

			}
			if(assetConfig.getStandardError().getShow()){

				XYSeries series = new XYSeries("Asset Std. Err. "+i);
				for(Integer betaCount=CAPM_FIRST_READ;betaCount<assetBetas.size();betaCount++){
					series.add( (double)betaCount,assetBetas.get(betaCount).getStandardError());
				}
				assetStandardErr.addSeries(series);

			}
			if(assetConfig.getVolatility().getShow()){
				List<Double> vol = new ArrayList<Double>();
				for(Double variance:traderInformation.getAssetVariance()[i]){
					vol.add(Math.sqrt(variance));
				}

				populateDataset("Asset Volatility "+i, vol ,assetVolatility,true);
			}
			if(assetConfig.getExcessReturns().getShow()){
				populateDataset("Asset Excess return "+i,
						traderInformation.getAssetReturnsInclCashAndCapitalLessRiskFree()[i] ,assetExcessReturn,true);
			}
			if(assetConfig.getCashflow().getShow()){
				List<Double> temp = annualise(traderInformation, traderInformation.getAssetCashflows()[i]);
				populateDataset("Asset Div (annualised) "+i, temp ,assetCashflow,true);
			}

			if(assetConfig.getDivYield().getShow()){

				List<Double> temp = annualise(traderInformation, results.getTraderInformation().getAssetDivYield()[i]);
				populateDataset("Asset Div Yield "+i, temp,assetDivYield,true);
			}

		}

		//AM: now prepare the trader stuff...

		boolean showWorth = false;
		boolean showTotalHoldings=false;
		boolean showLeverageRatio=false;
		boolean showHoldingNumbers=false;

		for(Class traderType:config.getTradersConfig().keySet()){
			TraderDisplayConfig traderConfig = config.getTradersConfig().get(traderType);
			showWorth = showWorth||traderConfig.getWorth().getShow();
			showLeverageRatio=showLeverageRatio||traderConfig.getLeverageRatio().getShow();
			showTotalHoldings = showTotalHoldings||traderConfig.getHoldings().getShow();
			showHoldingNumbers = showHoldingNumbers||traderConfig.getHoldingsNumber().getShow();
		}

		XYSeriesCollection traderWorth = new XYSeriesCollection();
		if(showWorth){
			constructAssetResultAxis(yaxiscount++, plot, "Net Worth", traderWorth,false,null);
		}
		XYSeriesCollection traderLeverage = new XYSeriesCollection();
		if(showLeverageRatio){
			constructAssetResultAxis(yaxiscount++, plot, "Leverage", traderLeverage,false,null);
		}
		XYSeriesCollection traderHoldings = new XYSeriesCollection();
		if(showTotalHoldings){
			constructAssetResultAxis(yaxiscount++, plot, "Portfolio Holding (Value)", traderHoldings,false,null);
		}
		XYSeriesCollection traderHoldingsNumber = new XYSeriesCollection();
		if(showHoldingNumbers){
			constructAssetResultAxis(yaxiscount++, plot, "Portfolio Holding (Number)", traderHoldingsNumber,false,null);
		}

		for(Class traderType:config.getTradersConfig().keySet()){
			TraderDisplayConfig traderConfig = config.getTradersConfig().get(traderType);

			if(traderConfig.getWorth().getShow()){
				DayMarketInfo[] marketResults = results.getResults();
				XYSeries series = new XYSeries(traderType.getSimpleName()+" worth");
				for(Integer i=0;i<marketResults.length;i++){
					Position pos = marketResults[i].getClassPositions().get(traderType);
					if(pos!=null){
						series.add((double) i,
								pos.getWorth(marketResults[i].getMarketPrices()));
					}
				}
				traderWorth.addSeries(series);
			}

			if(traderConfig.getLeverageRatio().getShow()){
				DayMarketInfo[] marketResults = results.getResults();
				XYSeries series = new XYSeries(traderType.getSimpleName()+" leverage");
				for(Integer i=0;i<marketResults.length;i++){
					Position pos = marketResults[i].getClassPositions().get(traderType);
					if(pos!=null){
						series.add((double) i,
								pos.getNominalAssetValue(marketResults[i].getMarketPrices())/pos.getWorth(marketResults[i].getMarketPrices())

						);
					}
				}
				traderLeverage.addSeries(series);
			}

			if(traderConfig.getHoldings().getShow()){
				DayMarketInfo[] marketResults = results.getResults();
				XYSeries series = new XYSeries(traderType.getSimpleName()+" total holdings value");
				Map<Integer, XYSeries> assetToHoldings = new HashMap<Integer, XYSeries>();
				for(Integer i=0;i<marketResults.length;i++){
					Position pos = marketResults[i].getClassPositions().get(traderType);
					if(pos!=null){
						double totalHolding =0;
						for(int assetRef=0;assetRef<results.getTraderInformation().getNumberOfAssets();assetRef++){
							XYSeries assetHolding = assetToHoldings.get(assetRef);
							if(assetHolding==null){
								assetHolding = new XYSeries(traderType.getSimpleName()+" holdings value "+assetRef);
								assetToHoldings.put(assetRef, assetHolding);
								traderHoldings.addSeries(assetHolding);
							}
							double marketValue=pos.getExecutedQuantity(assetRef)*(marketResults[i].getMarketPrice(assetRef));
							assetHolding.add((double)i,marketValue);
							totalHolding+=	marketValue;
						}
						series.add((double) i,totalHolding);
					}
				}
				traderHoldings.addSeries(series);
			}
			
			if(traderConfig.getHoldingsNumber().getShow()){
				DayMarketInfo[] marketResults = results.getResults();
				XYSeries series = new XYSeries(traderType.getSimpleName()+" total holdings number");
				Map<Integer, XYSeries> assetToHoldings = new HashMap<Integer, XYSeries>();
				for(Integer i=0;i<marketResults.length;i++){
					Position pos = marketResults[i].getClassPositions().get(traderType);
					if(pos!=null){
						double totalHolding =0;
						for(int assetRef=0;assetRef<results.getTraderInformation().getNumberOfAssets();assetRef++){
							XYSeries assetHolding = assetToHoldings.get(assetRef);
							if(assetHolding==null){
								assetHolding = new XYSeries(traderType.getSimpleName()+" holdings number "+assetRef);
								assetToHoldings.put(assetRef, assetHolding);
								traderHoldings.addSeries(assetHolding);
							}
							int numberHolding = pos.getExecutedQuantity(assetRef);
							assetHolding.add((double)i,numberHolding);
							totalHolding+=	numberHolding;
						}
						series.add((double) i,totalHolding);
					}
				}
				traderHoldingsNumber.addSeries(series);
			}

		}


		return lineChart;
	}


	public void displayLineChart(JFreeChart lineChart){
		if(chartPanel!=null){
			chartPanel.setChart(lineChart);
		}
	}

	private List<Double> annualise(MarketInformation traderInformation,
			List annualise) {
		List<Double> temp = (List<Double>)((ArrayList<Double>)annualise).clone();
		for(int i=0;i<temp.size();i++){
			Double number = temp.get(i);
			temp.set(i, number*traderInformation.getPeriodsInYear());
		}
		return temp;
	}

	private void constructAssetResultAxis(int count, XYPlot plot, String title,
			XYSeriesCollection assetPrices,boolean logarithmic,ValueAxis axis) {
		constructAssetResultAxis(count, plot, title, assetPrices, logarithmic, axis, false);
	}

	private void constructAssetResultAxis(int count, XYPlot plot, String title,
			XYSeriesCollection assetPrices,boolean logarithmic,ValueAxis axis,boolean bars) {
		ValueAxis assetPriceAxis = axis==null?(logarithmic?new LogarithmicAxis("Log "+title):new NumberAxis(title)):axis;
		plot.setRangeAxis(count, assetPriceAxis);
		plot.setDataset(count, assetPrices);
		plot.mapDatasetToRangeAxis(count, count);
		plot.setRenderer(count,bars?new XYBarRenderer(): new XYLineAndShapeRenderer(true,false));
	}

	private void drawPlot(int count, XYPlot plot, List<Double> values,
			String title, String axisTitle) {
		drawPlot(count, plot, values,
				title, axisTitle,null,null);

	}

	private void drawPlot(int count, XYPlot plot, List<Double> values,
			String title, String axisTitle,ValueAxis axis,XYItemRenderer render) {	
		plot.setDataset(count, createXYDataset(title, values ,render==null));
		plot.mapDatasetToRangeAxis(count, count);

		//final BarRenderer barRenderer = new BarRenderer();
		plot.setRenderer(count, render==null?new XYLineAndShapeRenderer(true,false):render);

		if(axis ==null){
			axis = new NumberAxis(axisTitle);
		}
		plot.setRangeAxis(count, axis);
	}

	private XYSeriesCollection createXYDataset(String name, List<Double> data,boolean wantZero){
		XYSeriesCollection ret = new XYSeriesCollection();
		populateDataset(name, data, ret, wantZero);
		return ret;
	}

	private void populateDataset(String name, List<Double> data,XYSeriesCollection ret,boolean wantZero){
		XYSeries series = new XYSeries(name);
		int i=wantZero?0:1;
		for(;i<data.size();i++){
			series.add( i,data.get(i));
		}
		ret.addSeries(series);
	}

}
